package com.google.code.jira.script.workflow;

import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

import org.apache.log4j.Category;

import com.atlassian.core.user.UserUtils;
import com.atlassian.jira.issue.Issue;
import com.opensymphony.user.EntityNotFoundException;
import com.opensymphony.user.User;
import com.opensymphony.workflow.WorkflowContext;
import com.opensymphony.workflow.WorkflowException;

public class ScriptWorkflowUtils {

	public static final String ISSUE_KEY = "issue";

	public static final String ORIGINAL_ISSUE_KEY = "originalissueobject";

	public static final String CONTEXT_KEY = "context";

	public static final String SCRIPT_NAME = "scriptName";

	public static final String ENGINE_NAME = "engineName";

	public static final String SCRIPT_CONTENT = "scriptContent";

	public static final Issue getIssue(Map transientArgs) {
		return (Issue) transientArgs.get(ISSUE_KEY);
	}

	public static final Issue getOriginalIssue(Map transientArgs) {
		return (Issue) transientArgs.get(ORIGINAL_ISSUE_KEY);
	}

	public static final User getCaller(Map transientArgs) {
		WorkflowContext context = (WorkflowContext) transientArgs.get(CONTEXT_KEY);
		String username = context.getCaller();

		try {
			return UserUtils.getUser(username);
		} catch (EntityNotFoundException e) {
			return null;
		}
	}

	public static String getScriptName(Map args) {
		return (String) args.get(SCRIPT_NAME);
	}

	public static String getEngineName(Map args) {
		return (String) args.get(ENGINE_NAME);
	}

	public static String getScriptContent(Map args) {
		return (String) args.get(SCRIPT_CONTENT);
	}

	public static ScriptEngine getEngine(Map args) throws WorkflowException {
		String engineName = getEngineName(args);
		if (engineName == null) {
			throw new WorkflowException("Missing parameter: engine name.");
		}

		// Loads the engine
		ScriptEngineManager manager = new ScriptEngineManager();
		List factories = manager.getEngineFactories();
		if (factories == null || factories.size() == 0) {
			throw new WorkflowException("No engines found! Please check your JVM version and configuration.");
		}

		ScriptEngine engine = null;
		for (Iterator iterator = factories.iterator(); iterator.hasNext();) {
			ScriptEngineFactory factory = (ScriptEngineFactory) iterator.next();
			if (engineName.equals(factory.getEngineName())) {
				engine = factory.getScriptEngine();
				break;
			}
		}

		if (engine == null) {
			throw new WorkflowException("No script engine found associate with the name " + engineName + ".");
		}

		return engine;
	}

	public static Object executeScript(Map transientVars, Map args, Category log) throws WorkflowException {
		// Obtains the issue and the caller
		Issue issue = getIssue(transientVars);
		Issue originalIssue = getOriginalIssue(transientVars);
		User caller = getCaller(transientVars);

		// Obtains the script engine and the script content
		ScriptEngine engine = getEngine(args);
		String scriptContent = getScriptContent(args);

		try {
			engine.eval(scriptContent);
			engine.put("issue", issue);
			engine.put("originalissue", originalIssue);
			engine.put("caller", caller);
			engine.put("log", log);

			Invocable invocable = (Invocable) engine;
			return invocable.invokeFunction("execute", null);
		} catch (ScriptException e) {
			throw new WorkflowException("Error evaluating script.", e);
		} catch (NoSuchMethodException e) {
			throw new WorkflowException("The method execute() has not been found.", e);
		}
	}
}
